
package com.opensymphony.clickstream;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import com.probiz.estore.webapp.util.RequestUtil;

/**
 * Stream of clicks tracked during a user's navigation through a site. Mainly
 * targeted to be used in admin system. Only "GET" requests should be saved.
 * 
 */
public class Clickstream implements Serializable {

	private static final long			serialVersionUID	= -9040452811786273131L;

	// click stream tag for current user
	public final static String			USER_CLICKSTREAM	= "user_clickstream";

	/** Flag indicating this is a bot surfing the site. */
	private boolean						bot					= false;

	/**
	 * 记住当前找到的上一个view的位置
	 */
	private int							gobackViewIdx		= -1;

	// these are attributes shared by all ClickRequests
	/** The host name. */
	private String						hostname;

	/** The original referer URL, if any. */
	private String						initialReferrer;

	/** The time of the last request made on this stream. */
	private Date						lastRequestDate		= new Date();

	/** The stream itself: a list of click events. */
	private List<ClickstreamRequest>	savedRequestList	= Collections
																	.synchronizedList(new ArrayList<ClickstreamRequest>());

	/** The stream start time. */
	private Date						startRequestDate	= new Date();

	public Clickstream() {
	}

	/**
	 * Adds a new request to the stream of clicks. The HttpServletRequest is
	 * converted to a ClickstreamRequest object and added to the clickstream.
	 * 
	 * @param request
	 *            The serlvet request to be added to the clickstream
	 */
	public void addRequest(HttpServletRequest request) {
		// if there is a boot ,then return immediately
		if (bot)
			return;

		lastRequestDate = new Date();

		// is new request ? (not same as last saved requests)
		boolean isNewRequest = true;

		String url = RequestUtil.getFullRequestUrl(request);

		// if this is the first request in the click stream
		if (savedRequestList.size() == 0) {
			// setup initial referrer
			if (request.getHeader("REFERER") != null) {
				initialReferrer = request.getHeader("REFERER");
			} else {
				initialReferrer = "";
			}
			// decide whether this is a bot
			bot = BotChecker.isBot(request);

			// Use ip instead of hostname to improve performance, Legend.
			hostname = request.getRemoteAddr();
		} else {// not the first request, add new request or update request
			// compare with last url, check if is the same
			ClickstreamRequest lastSavedRequest = getLatestRequest();
			if (url.equals(lastSavedRequest.getUrl())) {// is the same as last
				// request
				lastSavedRequest.setClickCounts(lastSavedRequest
						.getClickCounts() + 1);
				isNewRequest = false;
			}

			int interval = (int) (lastRequestDate.getTime() - lastSavedRequest
					.getTimestamp().getTime()) / 1000;
			lastSavedRequest.setTimeInterval(interval);
		}
		if (isNewRequest) {
			ClickstreamRequest currentRequest = new ClickstreamRequest(request,
					lastRequestDate);
			if (gobackViewIdx > -1
					&& currentRequest.getUrl().equals(
							getRequestAt(gobackViewIdx).getUrl())) {
				/**
				 * this is a returning view, i.e. PrevView
				 */
				currentRequest.setPrevRequestIndex(getRequestAt(gobackViewIdx)
						.getPrevRequestIndex());
				gobackViewIdx = -1;
			} else {
				int currentRequestIndex=(savedRequestList.size() - 1<0)?0:(savedRequestList.size() - 1);
				currentRequest.setPrevRequestIndex(currentRequestIndex);
			}
			savedRequestList.add(currentRequest);
		}
	}

	/**
	 * Returns the host name that this clickstream relates to.
	 * 
	 * @return the host name that the user clicked through
	 */
	public String getHostname() {
		return hostname;
	}

	/**
	 * The URL of the initial referer. This is useful for determining how the
	 * user entered the site.
	 * 
	 * @return the URL of the initial referer
	 */
	public String getInitialReferrer() {
		return initialReferrer;
	}

	/**
	 * Returns the last Date that the clickstream was modified.
	 * 
	 * @return the last Date that the clickstream was modified
	 */
	public Date getLastRequestDate() {
		return lastRequestDate;
	}

	/**
	 * 即使是PrevView，也会新增加一个request，所以永远取最后一个
	 * 
	 * @return
	 */
	public ClickstreamRequest getLatestRequest() {
		return (ClickstreamRequest) savedRequestList.get(savedRequestList
				.size() - 1);
	}

	public String getPrevView(String currentUrl) {
		String previousViewUrl = null;
		/**
		 * 上一个View的位置（注意：ClickstreamFilter在chain.doFilter后才增加新的Request）。
		 */
		int idx = savedRequestList.size() - 1;
		ClickstreamRequest request = (ClickstreamRequest) savedRequestList
				.get(idx);

		while (request != null && request.getUrl() != null
				&& previousViewUrl == null) {
			// 有指定返回什么View
			String returnView = request.getReturnView();
			if (returnView != null && !"".equals(returnView)) {
				if ("previous".equals(returnView)) {
					// 说明：这里不再处理与当前view相同的问题
					idx = request.getPrevRequestIndex();
					request = (ClickstreamRequest) savedRequestList.get(idx);
					previousViewUrl = request.getUrl();
				} else {
					// returnView == success or parent or specified url, caller
					// should handle them
					previousViewUrl = returnView;
				}
			} else {// 没有指定返回什么view，走正常处理流程，返回该请求的url
				// 放弃与当前请求相同的view（分页会造成相同的view，另form的请求也会导致出现一次重复），寻找下一个
				if (request.getUrl().startsWith(currentUrl)) {
					idx = request.getPrevRequestIndex();
					request = (ClickstreamRequest) savedRequestList.get(idx);
					continue;
				}
				previousViewUrl = request.getUrl();
			}
		}

		/**
		 * 这是为下个请求保存的，下个请求就是PrevView回复的那个请求，处理下个请求的时候，
		 * 会将这个当前找到的prevView的idx作为下个请求的PrevRequestIndex,因为（这些数据在session中保存，因此跨Request）.
		 */
		gobackViewIdx = idx;

		return previousViewUrl;
	}

	public ClickstreamRequest getRequestAt(int idx) {
		return (ClickstreamRequest) savedRequestList.get(idx);
	}

	/**
	 * Returns the Date when the clickstream began.
	 * 
	 * @return the Date when the clickstream began
	 */
	public Date getStartDate() {
		return startRequestDate;
	}

	/**
	 * Returns the actual List of ClickstreamRequest objects.
	 * 
	 * @return the actual List of ClickstreamRequest objects
	 */
	public List getStream() {
		return savedRequestList;
	}

	/**
	 * Returns the bot status.
	 * 
	 * @return true if the client is bot or spider
	 */
	public boolean isBot() {
		return bot;
	}

}
